/**
This module creates a box (box).
The box itself can have rounded corners/edges on the innside/outside.
This serves two purposes:
    1. Better Design/Looks - rounded outside edges influence the appearence. 
    2. Stability of the box - rounded inside corners make the whole box more rugged.
 All measurments are given in milimeter.
*/

// -> increase the number for smoother corners and edges
$fn = 40;

// -> what should be generated?
objectType="Box"; // [Box, Lid]

/* [Basic data] */
// -> thickness of the walls
wallthickness = 2.0;
// -> length of front (x-axis) of the box
length = 70;
// -> width (=length of sides = y-axis) of the box
width  = 160; 
// -> heigth of the box (ignored for lids)
height = 50;
// -> make a stackable box? (ignored for lids)
stackable = true;

/* [Roundings] */
// -> corner radius on the inside of the box (0 for unrounded inside corners)
cornerRadius = 2.0;
// -> edge radius on the outside of the box (0 for unrounded outside edges)
edgeRadius = 2.0;

/* [Compartments evenly spread] */
// -> number of compartments evenly spread on the y-axis parallel to the x-axis. Use 0 for none.
nrEvenXCompartments=3; 
// -> number of compartments evenly spread on the x-axis parallel to the y-axis. Use 0 for none.
nrEvenYCompartments=1; 

/* [Compartments user-defined] */
// -> vector of locations of compartments spread on the y-axis parallel to the x-axis. Use 0 for none.
customXCompartmentsAt=[]; 
// -> vector of locations of compartments spread on the x-axis parallel to the y-axis. Use 0 for none.
customYCompartmentsAt=[]; 

// -> Where should the compartments be placed.
//compartmentsAt=[]; 

/* [Handle] */
// -> diameter of the hole used as handle when box is used as drawer. Use 0 for no handle.
handleDiameter=0; 

/* [Hidden] */
// -> coordinates where the center of the handle is located.
location=[length/2, 0, (height/5)*3.5]; 

// -> height of interlocking part of a lid that goes into inner section of a box 
interlockHeight=wallthickness; 


//////////////////////////////////////////////////////////////////////7/////////////////////
// main
//////////////////////////////////////////////////////////////////////7/////////////////////

if (objectType=="Box")
{
    makeStackable()
    {
        makeCustomXCompartments()
        {
            makeCustomYCompartments()
            {
                makeEvenXCompartments()
                {
                    makeEvenYCompartments()
                    {
                        makeHandleWithHole()
                        {
                            box();
                        }
                    }
                }
            }
        }
    }
}
else makeLid();

//////////////////////////////////////////////////////////////////////7/////////////////////
// modules
//////////////////////////////////////////////////////////////////////7/////////////////////

/**
* module creates a lid
*/
module makeLid()
{
    union()
    {
        // lid
        if (edgeRadius > 0)   // box with rounded edges
        { 
            difference()
            {
                roundedBox(length, width, wallthickness, edgeRadius);
                translate([-1,-1,wallthickness])
                cube([length+2, width+2, wallthickness*2]);
            }
        }
        else  // straight box without rounded edges
        {
            cube([length, width, wallthickness]);
        }

        // inner part for interlocking with top opening of box
        translate([wallthickness+0.1, wallthickness+0.1,wallthickness-0.1])
        if (cornerRadius > 0)   // box with rounded edges
        { 
          roundedBox1(length-2*wallthickness-0.2, width-2*wallthickness-0.2,wallthickness+0.1 , cornerRadius);
        }
        else  // straight box without rounded edges
        {
            cube([length-2*wallthickness, width-2*wallthickness, interlockHeight]);
        }
    }
}

/**
* module creates a box
*/
module box()
{
    // check parameters
    assert(length>0, "Length of front cannot be zero or less!");
    assert(width>0, "Length of sides cannot be zero or less!");
    assert(height>0, "Height of box cannot be zero or less!");
    assert(wallthickness>0, "Thickness of the walls cannot be zero or less!");
    assert(cornerRadius>=0, "Radius of the inside corners cannot be less than zero!");
    assert(edgeRadius>=0, "Radius of the outside edges cannot be less than zero!");
    assert(edgeRadius<=cornerRadius, "Radius of the outside edges cannot be smaller than radius of inside corners!");

    // cut out inner box from outer box
    difference()
    {
        // outer box
        if (edgeRadius > 0)   // box with rounded edges
        { 
            roundedBox(length, width, height, edgeRadius);
        }
        else  // straight box without rounded edges
        {
            cube([length, width, height]);
        }
        
        // inner box
        translate([wallthickness, wallthickness, wallthickness])
        if (cornerRadius > 0)   // box with rounded edges
        { 
            roundedBox(length-2*wallthickness, width-2*wallthickness, height, cornerRadius);
        }
        else  // straight box without rounded edges
        {
            cube([length-2*wallthickness, width-2*wallthickness, height]);
        }
    }
}

/**
* make stackable part
*/
module makeStackable()
{
    if (stackable)
    {
        union()
        {
            translate([0,0,wallthickness]) children(0);
            // inner interlocking part
            translate([wallthickness+0.1, wallthickness+0.1, 0])
            if (cornerRadius > 0)   // box with rounded edges
            { 
                roundedBox1(length-2*wallthickness-0.2, 
                width-2*wallthickness-0.2,wallthickness+0.1 , cornerRadius);
            }
            else  // straight box without rounded edges
            {
                cube([length-2*wallthickness-0.2, width-2*wallthickness-0.2, interlockHeight+0.1]);
            }
        }
    }
    else { children(0); }
}

/**
* make compartments that are evenly spread on the y-axis parallel to the x-axis
*/
module makeEvenXCompartments()
{
   if (nrEvenXCompartments > 1)  // 0 and 1 would mean there are no compartments at all.
    {
        union()
        {
            compartmentLen = (width-2*wallthickness) / nrEvenXCompartments;

            // compartment
            if (edgeRadius > 0)   // box with rounded edges
            {
                for (i=[1:nrEvenXCompartments-1])
                    translate([0.01,i*compartmentLen+wallthickness/2,0.1])
                        translate([0, -2*wallthickness,0])
                            intersection()
                            {
                                roundedBox(length, width, height, edgeRadius);
                                translate([0, 2*wallthickness,0]) 
                                    cube([length-0.2, wallthickness, height-interlockHeight-0.2]);
                            }
            }
            else  // straight box without rounded edges
            {
                for (i=[1:nrEvenXCompartments-1])
                    translate([0.1,i*compartmentLen+wallthickness/2,0.1])
                        cube([length-0.2, wallthickness, height-interlockHeight-0.2]);
            }

            children(0);
        }
    }
    else { children(0); }
}

/**
* make compartments that are evenly spread on the x-axis parallel to the y-axis
*/
module makeEvenYCompartments()
{
   if (nrEvenYCompartments > 1)  // 0 and 1 would mean there are no compartments at all.
    {
        union()
        {
            compartmentLen = (length-2*wallthickness) / nrEvenYCompartments;

            // compartment
            if (edgeRadius > 0)   // box with rounded edges
            {
                for (i=[1:nrEvenYCompartments-1])
                    translate([i*compartmentLen+wallthickness/2, 0.01, 0.1])
                        translate([-2*wallthickness, 0 ,0])
                            intersection()
                            {
                                roundedBox(length, width, height, edgeRadius);
                                translate([2*wallthickness,0,0]) 
                                    cube([wallthickness, width-0.2, , height-interlockHeight-0.2]);
                            }
            }
            else  // straight box without rounded edges
            {
                for (i=[1:nrEvenYCompartments-1])
                    translate([i*compartmentLen+wallthickness/2, 0.1, 0.1])
                        cube([wallthickness, width-0.2, height-interlockHeight-0.2]);
            }

            children(0);
        }
    }
    else { children(0); }
}

/**
* make custom compartments that are evenly spread on the y-axis parallel to the x-axis as chosen by user
*/
module makeCustomXCompartments()
{
    if (customXCompartmentsAt)
    {
        union()
        {
            // compartment
            if (edgeRadius > 0)   // box with rounded edges
            {
                for (i=customXCompartmentsAt)
                    translate([0.01, i, 0.1])
                        translate([0, -2*wallthickness,0])
                            intersection()
                            {
                                roundedBox(length, width, height, edgeRadius);
                                translate([0, 2*wallthickness,0]) 
                                    cube([length-0.2, wallthickness, height-interlockHeight-0.2]);
                            }
            }
            else  // straight box without rounded edges
            {
                for (i=customXCompartmentsAt)
                    translate([0.01, i, 0.1])
                        cube([length-0.2, wallthickness, height-interlockHeight-0.2]);
            }

            children(0);
        }
    }
    else { children(0); }
}


/**
* make custom compartments that are evenly spread on the x-axis parallel to the y-axis as chosen by user
*/
module makeCustomYCompartments()
{
    if (customYCompartmentsAt)
    {
        union()
        {
            // compartment
            if (edgeRadius > 0)   // box with rounded edges
            {
                for (i=customYCompartmentsAt)
                    translate([i, 0.01, 0.1])
                        translate([-2*wallthickness,0,0])
                            intersection()
                            {
                                roundedBox(length, width, height, edgeRadius);
                                translate([2*wallthickness,0,0]) 
                                    cube([wallthickness, width-0.2, height-interlockHeight-0.2]);
                            }
            }
            else  // straight box without rounded edges
            {
                for (i=customYCompartmentsAt)
                    translate([i, 0.1, 0.1])
                        cube([wallthickness, width-0.2, height-interlockHeight-0.2]);
            }

            children(0);
        }
    }
    else { children(0); }
}


/**
* make a hole into the front wall of the box that servers as a handle.
*/
module makeHandleWithHole()
{
    if (handleDiameter != 0)
    {
        difference()
        {
            children(0);
            translate([location[0],wallthickness-0.1,location[2]]) 
                rotate([90, 0, 0]) 
                    cylinder(2*wallthickness, d=handleDiameter, center=true, $fn=60);
        }
    }
    else
    {
        children(0);
    }
}
////////////////////////////////////////////////////////////////////////////////////////
// creates a box with rounded edges all around except on the top
module roundedBox(length, width, height, radius)
{
    translate([radius,radius,radius])
    hull() 
    {
        sphere(radius);
        translate([length-2*radius, 0, 0])              sphere(radius);
        translate([length-2*radius, width-2*radius, 0]) sphere(radius);
        translate([0, width-2*radius, 0])               sphere(radius);
        
        translate([0, 0, height-radius])                            halfSphere(radius);
        translate([length-2*radius, 0, height-radius])              halfSphere(radius);
        translate([length-2*radius, width-2*radius, height-radius]) halfSphere(radius);
        translate([0, width-2*radius, height-radius])               halfSphere(radius);
    }
}
////////////////////////////////////////////////////////////////////////////////////////
// creates a box where only the vertical edges are rounded
module roundedBox1(length, width, height, radius)
{
    linear_extrude(height)
    {
        hull() 
        {
            translate([radius,radius])               circle(r=radius);
            translate([length-radius, radius])       circle(r=radius);
            translate([length-radius, width-radius]) circle(r=radius);
            translate([radius, width-radius])        circle(r=radius);
        }
    }    
}
////////////////////////////////////////////////////////////////////////////////////////
// creates half of a sphere
module halfSphere(radius)
{
    difference()
    {
        sphere(radius);
        translate([-1.5*radius, -1.5*radius, 0]) cube(3*radius);
    }
}
